﻿#region Using Statemens
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using GameBase.Graphics;
using GameBase.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StarFox.Graphics;
using StarFox.ScreenComponents;
#if DEBUG
using StarFox.Testing;
#endif
#endregion

namespace StarFox.Screens
{
    /// <summary>
    /// Basic properties needed to implement any game screen.
    /// </summary>
    public abstract class StarFoxScreen : Screen
    {
        #region Fields

        GraphicsConfig graphicsConfig;
        PostScreenProcessor postScreenProcessor;
        DisplayProcessor displayProcessor;  

        #endregion

        #region RenderTargets

        /// <summary>
        /// Gets the RenderTarget2D on which all content will be drawn prior to being finally displayed on the back buffer.
        /// </summary>
        protected RenderTarget2D DisplayBuffer
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the RenderTarget2D on which all 3D content of the Screen will be drawn.
        /// <para>Use this as an input for any posterior post-processing.</para>
        /// </summary>
        protected RenderTarget2D SceneBuffer
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the RenderTarget2D on which post-processing results will be drawn.
        /// <para>Use this as an input for drawing any 2D content of the Screen.</para>
        /// </summary>
        protected RenderTarget2D PostProcBuffer
        {
            get;
            private set;
        }

        #endregion

        #region Screen Components

        // list initially empty...
        readonly List<ScreenComponent2D> scrComp2D = new List<ScreenComponent2D>();

        /// <summary>
        /// Gets the list of ScreenComponent2D objects associated to the Screen. 
        /// </summary>
        protected List<ScreenComponent2D> Components2D
        {
            get { return scrComp2D; }
        }

        // list initially empty...
        readonly List<ScreenComponent3D> scrComp3D = new List<ScreenComponent3D>();

        /// <summary>
        /// Gets the list of ScreenComponent3D objects associated to the Screen.
        /// </summary>
        protected List<ScreenComponent3D> Components3D
        {
            get { return scrComp3D; }
        }

        #endregion

        #region Screen Transitions

        /// <summary>
        /// Makes the Screen transition to visible state.
        /// </summary>
        public override void Show()
        {
            foreach (ScreenComponent2D scrComp in Components2D)
                scrComp.Show();
        }

        /// <summary>
        /// Makes the Screen transition to hidden state.
        /// </summary>
        public override void Hide()
        {
            foreach (ScreenComponent2D scrComp in Components2D)
                scrComp.Hide();
        }

        #endregion

        #region Testing

#if DEBUG
        StringBuilder strBuffer;
        TestInput input;
        int outBuffer;
#endif

        #endregion

        #region Initialization

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="screenManager">The ScreenManager that this Screen is associated to.</param>
        public StarFoxScreen(ScreenManager screenManager)
            : base(screenManager)
        {
            graphicsConfig = Game.Services.GetService(typeof(GraphicsConfig)) as GraphicsConfig;
            if (graphicsConfig == null)
                throw new InvalidOperationException("Service of type GraphicsConfig not found");

            displayProcessor = Game.Services.GetService(typeof(DisplayProcessor)) as DisplayProcessor;
            if (displayProcessor == null)
                throw new InvalidOperationException("Service of type DisplayProcessingEffect not found");

            if (graphicsConfig.PostProcessing)
            {
                postScreenProcessor = Game.Services.GetService(typeof(PostScreenProcessor)) as PostScreenProcessor;
                if (postScreenProcessor == null)
                    throw new InvalidOperationException("Service of type PostScreenProcessor not found");
            }

            #region Testing

#if DEBUG
            strBuffer = Game.Services.GetService(typeof(StringBuilder)) as StringBuilder;
            if (strBuffer == null)
                throw new InvalidOperationException("Service of type StringBuilder not found");

            input = Game.Services.GetService(typeof(TestInput)) as TestInput;
            if (input == null)
                throw new InvalidOperationException("Service of type TestInput not found");
#endif

            #endregion
        }

        /// <summary>
        /// Called when the Screen needs to be initialized.
        /// </summary>
        public override void Initialize()
        {
            PresentationParameters pp = GraphicsDevice.PresentationParameters;

            if (graphicsConfig.AntiAliasing)
            {
                SceneBuffer = new RenderTarget2D(GraphicsDevice, Width, Height, false, pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
            }
            else
            {
                SceneBuffer = new RenderTarget2D(GraphicsDevice, Width, Height, false, pp.BackBufferFormat, pp.DepthStencilFormat);
            }

            if (graphicsConfig.PostProcessing)
            {
                PostProcBuffer = new RenderTarget2D(GraphicsDevice, Width, Height);
            }

            DisplayBuffer = new RenderTarget2D(GraphicsDevice, Width, Height);
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    #region RenderTargets

                    if (DisplayBuffer != null) { DisplayBuffer.Dispose(); DisplayBuffer = null; }
                    if (SceneBuffer != null) { SceneBuffer.Dispose(); SceneBuffer = null; }
                    if (PostProcBuffer != null) { PostProcBuffer.Dispose(); PostProcBuffer = null; }

                    #endregion

                    #region ScreenComponent2D

                    foreach (ScreenComponent2D scrComp in Components2D)
                    {
                        if (scrComp is IDisposable) { ((IDisposable)scrComp).Dispose(); }
                    }
                    Components2D.Clear();

                    #endregion

                    #region ScreenComponent3D

                    foreach (ScreenComponent3D scrComp in Components3D)
                    {
                        if (scrComp is IDisposable) { ((IDisposable)scrComp).Dispose(); }
                    }
                    Components3D.Clear();

                    #endregion

                    base.Dispose(disposing);
                }

                isDisposed = true;
            }
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the Screen needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Update(GameTime gameTime)
        {
            #region Screen Components

            // update 3D content only when the screen is not paused
            if (!Paused)
            {
                foreach (ScreenComponent3D scrComp in Components3D)
                    scrComp.Update(gameTime);
            }

            // update 2D content
            foreach (ScreenComponent2D scrComp in Components2D)
                scrComp.Update(gameTime);

            #endregion

            #region Testing
#if DEBUG
            if (input.IsShowPostProcBuffer(PlayerIndex)) { outBuffer = (outBuffer + 1) % 2; }

            if (graphicsConfig.PostProcessing)
            {
                switch (outBuffer)
                {
                    case 0:
                        {
                            strBuffer.AppendFormat("POSTPROCESSING - BLOOM\n\n");
                            break;
                        }
                    case 1:
                        {
                            strBuffer.AppendFormat("POSTPROCESSING - NO EFFECTS\n\n");
                            break;
                        }
                    default:
                        break;
                }
            }
            else
            {
                strBuffer.AppendFormat("NO POSTPROCESSING\n\n");
            }

            if (graphicsConfig.AntiAliasing) { strBuffer.AppendFormat("ANTIALIASING\n\n"); }
            else { strBuffer.AppendFormat("NO ANTIALIASING\n\n"); }
#endif
            #endregion
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when the Screen needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public sealed override void Draw(GameTime gameTime)
        {
            // draw 3D content
            Draw3D(gameTime);
            // post-scene processing
            PostProcess();
            // draw 2D content
            Draw2D(gameTime);
            // display all content on the screen
            Display();
        }

        #region Draw3D

        /// <summary>
        /// Called when 3D content of the Screen needs to be drawn.
        /// <para>Override this method with screen-specific draw code.</para>
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        protected virtual void Draw3D(GameTime gameTime)
        {
            // output to SceneBuffer
            GraphicsDevice.SetRenderTarget(SceneBuffer);
            GraphicsDevice.Clear(Color.Transparent);

            foreach (ScreenComponent3D scrComp in Components3D)
                scrComp.Draw(gameTime);
        }

        #endregion

        #region Post-Processing

        /// <summary>
        /// Performs post-scene processing after all 3D content of the Screen has been drawn.
        /// </summary>
        private void PostProcess()
        {
            if (!graphicsConfig.PostProcessing)
                return;

#if DEBUG
            switch (outBuffer)
            {
                case 0:
                    {
#endif
                        postScreenProcessor.Apply(SceneBuffer, PostProcBuffer);
#if DEBUG
                        break;
                    }
                default:
                    break;
            }
#endif
        }

        #endregion

        #region Draw2D

        /// <summary>
        /// Called when 2D content of the Screen needs to be drawn.
        /// <para>Override this method with screen-specific draw code.</para>
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        protected virtual void Draw2D(GameTime gameTime)
        {
            // output to DisplayBuffer
            GraphicsDevice.SetRenderTarget(DisplayBuffer);
            GraphicsDevice.Clear(Color.Transparent);

            SpriteBatch.Begin();

            if (graphicsConfig.PostProcessing)
            {
#if DEBUG
                if (outBuffer == 1)
                    SpriteBatch.Draw(SceneBuffer, new Rectangle(0, 0, Width, Height), Color.White);
                else
#endif
                    SpriteBatch.Draw(PostProcBuffer, new Rectangle(0, 0, Width, Height), Color.White);
            }
            else
            {
                SpriteBatch.Draw(SceneBuffer, new Rectangle(0, 0, Width, Height), Color.White);
            }

            // ... then draw the sprites over
            foreach (ScreenComponent2D scrComp in Components2D)
                scrComp.Draw(gameTime);

            SpriteBatch.End();
        }

        #endregion

        #region Display

        /// <summary>
        /// Displays all contents of the Screen.
        /// </summary>
        private void Display()
        {
            // screen transitions
            if (ScreenManager.State == ScreenManager.TransitionState.NO_TRANSITION)
                displayProcessor.Alpha = 1.0f;
            else if (ScreenManager.State == ScreenManager.TransitionState.TRANSITION_IN)
                displayProcessor.Alpha = (float)Math.Pow(ScreenManager.Position, 2);
            else if (ScreenManager.State == ScreenManager.TransitionState.TRANSITION_OUT)
                displayProcessor.Alpha = (float)Math.Pow(1.0f - ScreenManager.Position, 2);

            displayProcessor.Apply(DisplayBuffer, null);
        }

        #endregion

        #endregion
    }
}
